package hebClustering;
import hebClustering.vectorSpace.IVector;
import hebClustering.vectorSpace.distances.*;

import java.util.LinkedList;
import java.util.List;

/**
 * This class represents a set of clusters.
 * 
 * It includes common and necessary operations which are helpful in order to implement
 * clustering algorithms AND measure the result given by them.
 *
 */
public class ClusterSet extends LinkedList<Cluster> {

	private static final long serialVersionUID = 1L;

	/**
	 * Returns a cluster from the cluster collection which
	 * includes the given vector.
	 * 
	 * @param v - A vector
	 * 
	 * @return A cluster which the vector v is contained in, or null if it is not part of the set.
	 */
	public Cluster getCluster(IVector v) {
		for(Cluster cluster : this){
			if(cluster.contains(v))
				return cluster;
		}
		return null;
	}

	/**
	 * Removes all empty clusters from the set
	 */
	public void removeAllEmptyClusters()
	{
		List<Cluster> emptyClusters = new LinkedList<Cluster>();
		for(Cluster cluster : this){
			if(cluster.isEmpty()){
				emptyClusters.add(cluster);
			}
		}

		removeAll(emptyClusters);
	}

	/**
	 * Returns a measure quality for the cluster set.
	 *
	 * <p>This method returns a value which is a measure for the quality of the set.<br>
	 * the value is sum of SSE of each cluster in the set, and therefore, <br>
	 * for a lower value returned by the method, there is a higher probability that the resulting clustering is better.</p>
	 *
	 * @return the SSE over all the clusters in the cluster set.
	 */
	public double quality(){
		double totalSum = 0;

		for(Cluster cluster : this){
			totalSum+=cluster.getSSE();
		}

		return totalSum;
	}

	
	/**
	 * Merge two clusters.
	 *
	 * @param cluster1
	 * @param cluster2 
	 * 
	 * <p> Performs a merge of the two given clusters and turns them into one. </p>
	 */
	public void merge(Cluster cluster1, Cluster cluster2) {
		if (cluster1 == cluster2) return;
		
		cluster1.addAll(cluster2);
		remove(cluster2);
	}

	
	/**
	 * Returns a string representation.
	 *
	 * @return a string representation of the cluster set.
	 */
	public String toString(){
		StringBuffer output = new StringBuffer("\tClustering Criteria: " + quality() + "\r\n\r\n\t\t");

		for (Cluster cluster : this){
			output.append(cluster.toString() + "\r\n\t\t");		
		}

		return output.toString();
	}
	
	/**
	 * Sets a representative for every cluster in the set using the single-linkage method.
	 * 
	 * 	@see <a href="http://en.wikipedia.org/wiki/Single-linkage_clustering" target="_blank">Single-linkage clustering</a>
	 */
	public void setRepresentatives(){
		IDistance d = new ChebyshevDistance();
		for (Cluster c : this){
			double minDistanceToCentroid = Double.MAX_VALUE;
			double dist = minDistanceToCentroid;
			IVector centroid = c.findMedian();
			IVector representative = null;
			for (IVector v : c){
				if (representative == null){
					representative = v;
					minDistanceToCentroid = d.calc(centroid, v);
				}else{
					dist = d.calc(centroid, v);
					//					if (dist == minDistanceToCentroid){//TO DO and if they are equal??
					//						System.err.println("current representative: " + representative.toString() + "\r\n\tv: " + v.toString() + "\r\n\t dist to centroid: " + dist);
					//					}
					if (dist < minDistanceToCentroid){
						minDistanceToCentroid = dist;
						representative = v;
					}
				}
			}
			c.setRepresentative(representative);
		}
	}
	
}
